템플릿화한 HTML 문서와 Javascript 실행 문제
✒️ 2025-05-23 15:32 내용 수정
프로젝트와 문서 구조
- 템플릿 문서 :
topheader.html - 템플릿 적용 문서(이하 적용 문서) :
index.html을 포함한 페이지 문서 - 템플릿 용 JavaScript 파일 :
topheader.js
root/
├── html/
│ ├── index.html
│ └── topheader.html
├── js/
│ └── topheader/
│ └── topheader.js
- 프로젝트에서 구조적으로 반복 사용되는 요소(
header나 기타 반복 구조)를 템플릿화하여 사용하고 있었다. - 적용 문서에는 템플릿 문서를 넣을 요소가 있고, JQuery를 사용하여 템플릿 문서를 가져왔다.
공통 문제 상황
- Git branch를 통합하던 중 적용 문서에 템플릿 용 JavaScript 파일을 가져온 후 웹 브라우저에서 버튼 event로 메뉴 바가 보이거나 사라지는 동작을 수행했으나 CSS가 제대로 적용되지 않았다.
문제 1
- JavaScript 파일에 있는
console.log()로 요소들을 확인했을 때null로 처리가 됐다. - 템플릿 문서를 가져온 후 JavaScript 파일이 실행되면
sidebar와toggleButton요소가 탐지될 것으로 예상했으나 실제론 그러지 않았다.
문제 1의 원인과 해결 방법
문제와 연관된 항목들 참고 자료 : 웹 브라우저의 동작#3. 구문 분석(Parsing), JavaScript#defer와 async 속성, DOMContentLoaded
- 적용 문서에서 JavaScript 호출 시 이미 문서의 DOM은 로드가 되고, JavaScript에서 요청으로 가져온 템플릿 문서의 요소는 로드가 늦기 때문에 해당 요소들을 검색하려고 하면
null이 발생한다.index.html로딩 시작<script src="../js/includeHtml.js>실행 -> 비동기 동작으로topheader.html로드<script src="../js/topheader.js defer>실행 ->index.html문서 파싱 시 동시에 다운로드 시작index.html의 DOM 파싱이 끝나면<script src="../js/topheader.js defer>를DOMContentLoaded이벤트 이전에 실행topheader.html의 파싱은 끝나지 않아topheader.html의 요소를index.html에서 찾으려 하면null이 발생
- 따라서
DOMContentLoad이벤트 리스너에서 템플릿의 요소를 선택자로 가져올 때 해당 요소들이 로드될 때까지 반복해서 탐색하는setInterval()을 사용하여 요소를 지속 탐색한다. - 탐색이 완료되면 이벤트 리스너를 등록하고
clearInterval()로 interval을 초기화한다.
// side nav의 버튼 동작을 제어
document.addEventListener("DOMContentLoaded", function () {
// nav 요소가 로딩될 때까지 탐색
const interval = setInterval(() => {
console.log('nav 요소 탐색중..')
// 이벤트를 넣을 대상 요소 선택
const sidebar = document.querySelector("#side-bar");
const toggleButton = document.querySelector("#side-button");
console.log(sidebar, toggleButton);
// 요소가 있을 때 이벤트 추가
if (sidebar && toggleButton) {
toggleButton.addEventListener("click", function () {
sidebar.classList.toggle("active");
});
clearInterval(interval); // 이벤트 연결 후 멈춤
}
}, 100);
});
MutationObserver를 사용하는 방법도 있으나 현재 단계에선 개념을 이해하기 어려워 간단하게 코드 구조 설명만 듣고 넘어갔다.- 참고 자료 : mdn web docs MutationObserver
const observer = new MutationObserver(() => {
const sidebar = document.querySelector("#side-nav-bar");
const toggleButton = document.getElementById("SideButton");
if (sidebar && toggleButton) {
toggleButton.addEventListener("click", function () {
sidebar.classList.toggle("active");
});
observer.disconnect(); // 감지 중지
}
});
observer.observe(document.body, { childList: true, subtree: true });
- 또 다른 방법으로는
topheader.html를 가져오는includeHtml.js에서topheader.html을 가져오면서topheader.js의 이벤트 리스너 등록을 처리할 수 있다.- 아래 내용으로 수정하면
topheader.js파일을 사용하지 않고 이벤트 리스너를 등록할 수 있다. - 개발자 도구에서 성능 탭으로 LCP와 CLS를 비교했을 때 두 방법의 차이가 큰 편은 아니었다.
- 아래 내용으로 수정하면
document.addEventListener("DOMContentLoaded", function () {
// 템플릿 문서 가져오기
fetch("./topheader.html")
.then(response => response.text())
.then(html => {
// HTML을 삽입
const topHeader = document.getElementById("top-header");
topHeader.innerHTML = html;
// 이벤트를 넣을 대상 요소 선택
const sidebar = document.querySelector("#side-bar");
const toggleButton = document.querySelector("#side-button");
// 요소가 있을 때 이벤트 추가
toggleButton.addEventListener("click", function () {
sidebar.classList.toggle("active");
});
})
.catch(error => console.error("topheader 로드 실패:", error));
});
문제 2
- 템플릿 문서에 JavaScript 파일을 가져오는
<script>를 넣고, 템플릿 적용 문서에는 해당 파일의<script>태그가 없을 때 이벤트 동작이 일어나지 않는다. topheader.html문서에topheader.js를 스크립트 파일로 연결하고,index.html에서 제거했을 때 스크립트 자체가 작동하지 않았다.
문제 2의 원인과 해결 방법
- JavaScript로 HTML 문서를 가져올 때는 텍스트로 가져온다.
- 동적인 부분은 동작하지 않고 HTML과 CSS와 같은 정적 형태로만 가져온다.
- 따라서 템플릿 문서 내의
<script src="file">부분은 문자열로 가져오기 때문에 동작하지 않는다. - 그러므로 템플릿을 가져오는 HTML 문서에 템플릿 HTML 문서의 동작을 제어할 JavaScript 파일를 추가해야 한다.